#include "Mesh.h"

using namespace GalliumEngine;

Mesh::MeshEntry::MeshEntry()
{
    vbo = NULL;
    ibo = NULL;
    numIndices  = 0;
    materialIndex = INVALID_MATERIAL;
};

Mesh::MeshEntry::~MeshEntry()
{
    if (vbo != NULL)
    {
        glDeleteBuffers(1, &vbo);
    }

    if (ibo != NULL)
    {
        glDeleteBuffers(1, &ibo);
    }
}

void Mesh::MeshEntry::init(const std::vector<Vertex>& _vertices,
                          const std::vector<unsigned int>& _indices)
{
    numIndices = _indices.size();

    glGenBuffers(1, &vbo);
  	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * _vertices.size(), &_vertices[0], GL_STATIC_DRAW);

    glGenBuffers(1, &ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * numIndices, &_indices[0], GL_STATIC_DRAW);
}

Mesh::Mesh()
{
}


Mesh::~Mesh()
{
    clear();
}


void Mesh::clear()
{
}


bool Mesh::loadMesh(const std::string& Filename)
{
    // Release the previously loaded mesh (if it exists)
    clear();
    
    bool Ret = false;
    Assimp::Importer Importer;

    const aiScene* pScene = Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);
    
    if (pScene) 
	{
        Ret = initFromScene(pScene, Filename);
    }
    else 
	{
		Logger::getInstance()->write(StringUtils::format("Error parsing '%s': '%s'\n", Filename.c_str(), Importer.GetErrorString()));
    }

    return Ret;
}

bool Mesh::initFromScene(const aiScene* pScene, const std::string& Filename)
{  
    entries.resize(pScene->mNumMeshes);
    textures.resize(pScene->mNumMaterials);

    // Initialize the meshes in the scene one by one
    for (unsigned int i = 0 ; i < entries.size() ; i++) 
	{
        const aiMesh* paiMesh = pScene->mMeshes[i];
        initMesh(i, paiMesh);
    }

    return initMaterials(pScene, Filename);
}

void Mesh::initMesh(unsigned int index, const aiMesh* paiMesh)
{
    entries[index].materialIndex = paiMesh->mMaterialIndex;
    
    std::vector<Vertex> Vertices;
    std::vector<unsigned int> Indices;

    const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

    for (unsigned int i = 0 ; i < paiMesh->mNumVertices ; i++) 
	{
        const aiVector3D* pPos      = &(paiMesh->mVertices[i]);
        const aiVector3D* pNormal   = &(paiMesh->mNormals[i]);
        const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;

        Vertex v(Vector3(pPos->x, pPos->y, pPos->z),
                 Vector2(pTexCoord->x, pTexCoord->y),
                 Vector3(pNormal->x, pNormal->y, pNormal->z));

        Vertices.push_back(v);
    }

    for (unsigned int i = 0 ; i < paiMesh->mNumFaces ; i++) 
	{
        const aiFace& Face = paiMesh->mFaces[i];
        assert(Face.mNumIndices == 3);
        Indices.push_back(Face.mIndices[0]);
        Indices.push_back(Face.mIndices[1]);
        Indices.push_back(Face.mIndices[2]);
    }

    entries[index].init(Vertices, Indices);
}

bool Mesh::initMaterials(const aiScene* pScene, const std::string& Filename)
{
    // Extract the directory part from the file name
    std::string::size_type SlashIndex = Filename.find_last_of("/");
    std::string Dir;

    if (SlashIndex == std::string::npos) 
	{
        Dir = ".";
    }
    else if (SlashIndex == 0) 
	{
        Dir = "/";
    }
    else 
	{
        Dir = Filename.substr(0, SlashIndex);
    }

    bool Ret = true;

    // Initialize the materials
    for (unsigned int i = 0 ; i < pScene->mNumMaterials ; i++) 
	{
        const aiMaterial* pMaterial = pScene->mMaterials[i];

        textures[i] = NULL;

        if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) 
		{
            aiString Path;

            if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) 
			{
                std::string FullPath = Dir + "/" + Path.data;
                textures[i] = new Texture();

				if (!textures[i]->loadTexture(FullPath.c_str(),true))
				{
					Logger::getInstance()->write(StringUtils::format("Error loading texture '%s'\n", FullPath.c_str()));
                    delete textures[i];
                    textures[i] = NULL;
                    Ret = false;
                }
                else 
				{
					Logger::getInstance()->write(StringUtils::format("Loaded texture '%s'\n", FullPath.c_str()));
                }
            }
        }

        // Load a white texture in case the model does not include its own texture
        if (!textures[i]) 
		{
            textures[i] = new Texture();

			Ret = textures[i]->loadTexture("Resources/Textures/default_diffuse.dds",false);
        }
    }

    return Ret;
}

void Mesh::draw()
{
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    for (unsigned int i = 0 ; i < entries.size() ; i++) {
        glBindBuffer(GL_ARRAY_BUFFER, entries[i].vbo);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, entries[i].ibo);

        const unsigned int materialIndex = entries[i].materialIndex;

        if (materialIndex < textures.size() && textures[materialIndex]) 
		{
			textures[materialIndex]->bindTexture(GL_TEXTURE0);
        }

        glDrawElements(GL_TRIANGLES, entries[i].numIndices, GL_UNSIGNED_INT, 0);
    }

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
}